package com.yeskery.nut.core;

import com.yeskery.nut.http.controller.*;
import com.yeskery.nut.util.PathUtils;
import com.yeskery.nut.util.StringUtils;

import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

/**
 * Controller 管理器 {@link ControllerManager} 的默认实现类
 * @author sprout
 * 2019-03-16 10:01
 * @version 1.0
 *
 * @see com.yeskery.nut.core.ControllerManager
 */
public final class NutControllerManager implements ControllerManager {

	/** 动态路径前缀 */
	private static final String DYNAMIC_PATH_PREFIX = "$";

	/** 日志对象 */
	private static final Logger logger = Logger.getLogger(NutControllerManager.class.getName());

	/** 单例模式的 Controller 管理器 */
	private static volatile ControllerManager controllerManager = null;

	/** ControllerId 自增id生成器 */
	private final AtomicInteger atomicId = new AtomicInteger();

	/** Controller Path缓存 */
	private final Set<String> controllerPathCacheSet = new HashSet<>();

	/** 用于承载 Controller 的载体 */
	private final Map<ControllerMetadata, Controller> controllerRegisterMap;

	/** 用于缓存 Controller 的载体 */
	private final Map<String, Controller> controllerRegisterCacheMap;

	/** 用于缓存默认 Controller 的载体 */
	private final Map<String, Controller> defaultControllerMap;

	/** 用于缓存动态路径 Controller 的载体 */
	private final Set<DynamicControllerRegisterMetadata> dynamicControllerRegisterCacheSet;

	/** 用于存放静态资源路径的载体 */
	private final Set<Path> staticResourcePathSet = new HashSet<>();

	/** 用于存放静态资源路径的载体 */
	private final Set<String> staticResourceDirSet = new HashSet<>();

	/** 目录预览状态 */
	private boolean staticDirectoryPreview;

	/** 首页 Controller */
	private volatile Controller indexController;

	/** 错误 Controller */
	private volatile Controller errorController;

	/** 404 Controller */
	private volatile Controller notFoundController;

	/** 静态资源 Controller */
	private volatile StaticResourceController staticResourceController;

	{
		controllerRegisterMap = new ConcurrentHashMap<>();
		controllerRegisterCacheMap = new ConcurrentHashMap<>();
		defaultControllerMap = new HashMap<>(1);
		dynamicControllerRegisterCacheSet = new CopyOnWriteArraySet<>();
		//向 Controller 管理器注册一个 /favicon.ico 的Controller，默认的页面 icon
		Controller faviconController = new FaviconController();
		DefaultControllerMeta controllerMetadata = new DefaultControllerMeta(atomicId.incrementAndGet(), FaviconController.DEFAULT_PATH,
				new String[0], ControllerSource.DEFAULT);
		controllerRegisterMap.put(controllerMetadata, faviconController);
		controllerRegisterCacheMap.put(FaviconController.DEFAULT_PATH, faviconController);
		defaultControllerMap.put(FaviconController.DEFAULT_PATH, faviconController);
	}

	/** 私有化构造方法 */
	private NutControllerManager() {
	}

	@Override
	public Map<ControllerMetadata, Controller> getAllController() {
		return Collections.unmodifiableMap(controllerRegisterMap);
	}

	@Override
	public void registerController(Controller controller, ControllerSource controllerSource, String path, String[] alias) {
		if (controller == null) {
			throw new IllegalArgumentException("Controller Must Not Be Null.");
		}
		if (StringUtils.isEmpty(path)) {
			throw new IllegalArgumentException("Path Must Not Be Null.");
		}
		PathMetadata pathMetadata = PathUtils.parse(path);
		if (pathMetadata == null) {
			throw new IllegalArgumentException("Path Must Not Be Null.");
		}
		alias = alias == null ? new String[0] : alias;
		if (pathMetadata.isDynamic()) {
			String pathName = DYNAMIC_PATH_PREFIX + path;
			if (!controllerPathCacheSet.add(pathName)) {
				throw new NutException("Controller Path [" + path + "] Already Registered.");
			}
			DefaultControllerMeta controllerMetadata = new DefaultControllerMeta(atomicId.incrementAndGet(), pathName,
					alias, controllerSource);
			controllerRegisterMap.put(controllerMetadata, controller);
			DynamicControllerRegisterMetadata dynamicControllerRegisterMetadata = new DynamicControllerRegisterMetadata(
					Arrays.stream(pathMetadata.getParamNameAndPatterns()).map(KeyAndValue::getKey).toArray(String[]::new),
					new PathMatcher(pathMetadata.getPathPattern()), null);
			dynamicControllerRegisterMetadata.setController(new DynamicControllerAdapter(controller, dynamicControllerRegisterMetadata));
			dynamicControllerRegisterCacheSet.add(dynamicControllerRegisterMetadata);

			for (String aliasPath : alias) {
				if (!controllerPathCacheSet.add(aliasPath)) {
					throw new NutException("Controller Alias [" + aliasPath + "] Already Registered.");
				}
				PathMetadata aliasPathMetadata = PathUtils.parse(aliasPath);
				if (aliasPathMetadata == null || !aliasPathMetadata.isDynamic()
						|| aliasPathMetadata.getParamNameAndPatterns().length != pathMetadata.getParamNameAndPatterns().length) {
					throw new NutException("Path [" + path + "] And Alias Path [" + String.join(",", alias) + "] Not Match.");
				}

				dynamicControllerRegisterMetadata = new DynamicControllerRegisterMetadata(
						Arrays.stream(aliasPathMetadata.getParamNameAndPatterns()).map(KeyAndValue::getKey).toArray(String[]::new),
						new PathMatcher(aliasPathMetadata.getPathPattern()), null);
				dynamicControllerRegisterMetadata.setController(new DynamicControllerAdapter(controller, dynamicControllerRegisterMetadata));
				dynamicControllerRegisterCacheSet.add(dynamicControllerRegisterMetadata);
			}
		} else {
			if (!controllerPathCacheSet.add(path)) {
				throw new NutException("Controller Path [" + path + "] Already Registered.");
			}
			DefaultControllerMeta controllerMetadata = new DefaultControllerMeta(atomicId.incrementAndGet(), path,
					alias, controllerSource);
			controllerRegisterMap.put(controllerMetadata, controller);
			controllerRegisterCacheMap.put(path, controller);
			for (String aliasPath : alias) {
				if (!controllerPathCacheSet.add(aliasPath)) {
					throw new NutException("Controller Alias [" + aliasPath + "] Already Registered.");
				}
				pathMetadata = PathUtils.parse(aliasPath);
				if (pathMetadata == null || pathMetadata.isDynamic()) {
					throw new NutException("Path [" + path + "] And Alias Path [" + String.join(",", alias) + "] Not Match.");
				}
				controllerRegisterCacheMap.put(aliasPath, controller);
			}
		}
	}

	@Override
	public void registerController(RequestHandler requestHandler, ControllerSource controllerSource, Set<Method> methods, String path, String[] alias) {
		if (requestHandler == null) {
			throw new IllegalArgumentException("RequestHandler Must Not Be Null.");
		}
		LambdaController controller = new LambdaController();
		Controller proxyController = (Controller) Proxy.newProxyInstance(controller.getClass().getClassLoader(),
				controller.getClass().getInterfaces(),
				new FunctionControllerInvocationHandler(requestHandler, methods, controller));
		registerController(proxyController, controllerSource, path, alias);
	}

	@Override
	public boolean removeController(Controller controller) {
		if (controller == null) {
			return false;
		}
		boolean result = false;
		Iterator<Map.Entry<ControllerMetadata, Controller>> controllerIterator = controllerRegisterMap.entrySet().iterator();
		while (controllerIterator.hasNext()) {
			Map.Entry<ControllerMetadata, Controller> entry = controllerIterator.next();
			if (entry.getValue() == controller) {
				controllerIterator.remove();
				result = true;
				break;
			}
		}

		Iterator<Map.Entry<String, Controller>> cacheIterator = controllerRegisterCacheMap.entrySet().iterator();
		while (cacheIterator.hasNext()) {
			Map.Entry<String, Controller> entry = cacheIterator.next();
			if (entry.getValue() == controller) {
				cacheIterator.remove();
				result = true;
				break;
			}
		}

		Iterator<DynamicControllerRegisterMetadata> dynamicIterator = dynamicControllerRegisterCacheSet.iterator();
		while (dynamicIterator.hasNext()) {
			DynamicControllerRegisterMetadata metadata = dynamicIterator.next();
			if (metadata.getController() == controller) {
				dynamicIterator.remove();
				result = true;
				break;
			}
		}
		return result;
	}

	@Override
	public boolean removeController(long controllerId) {
		if (controllerId <= 0) {
			return false;
		}
		return controllerRegisterMap.entrySet().stream()
				.filter(e -> e.getKey().getId() == controllerId)
				.findFirst()
				.filter(e -> removeController(e.getValue()))
				.isPresent();
	}

	@Override
	public boolean removeController(String path) {
		if (StringUtils.isEmpty(path)) {
			return false;
		}
		return removeController(findController(path));
	}

	@Override
	public void addStaticResourcePaths(String... paths) {
		for (String path : paths) {
			staticResourcePathSet.add(new Path(path));
		}
	}

	@Override
	public void addStaticResourceDirs(String... dirs) {
		staticResourceDirSet.addAll(Arrays.asList(dirs));
	}

	@Override
	public Set<Path> getStaticResourcePaths() {
		return staticResourcePathSet;
	}

	@Override
	public Set<String> getStaticResourceDirs() {
		return staticResourceDirSet;
	}

	@Override
	public void setStaticDirectoryPreview(boolean staticDirectoryPreview) {
		this.staticDirectoryPreview = staticDirectoryPreview;
	}

	@Override
	public void setIndexController(Controller controller) {
		if (controller == null) {
			throw new IllegalArgumentException("Controller Must Not Be Null.");
		}
		indexController = controller;
	}

	@Override
	public void setErrorController(Controller controller) {
		if (controller == null) {
			throw new IllegalArgumentException("Controller Must Not Be Null.");
		}
		errorController = controller;
	}

	@Override
	public void setNotFoundController(Controller controller) {
		if (controller == null) {
			throw new IllegalArgumentException("Controller Must Not Be Null.");
		}
		notFoundController = controller;
	}

	@Override
	public void setStaticResourceController(StaticResourceController controller) {
		staticResourceController = controller;
		controller.setStaticResourceDirectories(staticResourceDirSet.toArray(new String[0]));
	}

	@Override
	public Controller findController(String path) {
		// 尝试获取默认controller
		Controller controller = findDefaultController(path);
		if (controller != null) {
			return controller;
		}
		// 尝试静态资源中获取Controller
		Path staticResourcePath = findStaticResourcePath(path);
		if (staticResourcePath != null) {
			StaticResourceController staticResourceController = getStaticResourceController();
			staticResourceController.setStaticResourceRequestPath(staticResourcePath);
			return staticResourceController;
		}

		// 尝试从静态路径中获取Controller
		controller = controllerRegisterCacheMap.get(path);
		if (controller == null) {
			// 尝试从动态路径中获取Controller
			for (DynamicControllerRegisterMetadata metadata : dynamicControllerRegisterCacheSet) {
				if (metadata.getPathMatcher().match(path)) {
					controller = metadata.getController();
					break;
				}
			}
		}
		if (controller == null) {
			logger.warning("Request Path[" + path + "] Can Not Find Corresponding Controller.");
		}
		return controller != null ? controller : "/".equals(path) ? getIndexController() : getNotFoundController();
	}

	@Override
	public Controller getController(String path) {
		Controller controller = controllerRegisterCacheMap.get(path);
		if (controller == null) {
			// 尝试从动态路径中获取Controller
			for (DynamicControllerRegisterMetadata metadata : dynamicControllerRegisterCacheSet) {
				if (metadata.getPathMatcher().match(path)) {
					controller = metadata.getController();
					break;
				}
			}
		}
		return controller;
	}

	@Override
	public Controller getIndexController() {
		if (indexController == null) {
			indexController = new DefaultController("Welcome To Sprout Nut");
		}
		return indexController;
	}

	@Override
	public Controller getErrorController() {
		if (errorController == null) {
			errorController = new DefaultErrorController(ResponseCode.INTERNAL_SERVER_ERROR);
		}
		return errorController;
	}

	@Override
	public Controller getNotFoundController() {
		if (notFoundController == null) {
			notFoundController = new DefaultErrorController(ResponseCode.NOT_FOUND);
		}
		return notFoundController;
	}

	@Override
	public StaticResourceController getStaticResourceController() {
		if (staticResourceController == null) {
			staticResourceController = new DefaultStaticResourceController(staticResourceDirSet.toArray(new String[0]));
			staticResourceController.setDirectoryPreviewStatus(staticDirectoryPreview);
		}
		return staticResourceController;
	}

	/**
	 * 以单例模式获取 {@link ControllerManager} 对象
	 * @return 单例模式的 {@link ControllerManager} 对象
	 */
	public static ControllerManager getInstance() {
		if (controllerManager == null) {
			synchronized (NutControllerManager.class) {
				if (controllerManager == null) {
					controllerManager = new NutControllerManager();
				}
			}
		}
		return controllerManager;
	}

	/**
	 * 查找静态资源路径
	 * @param path 请求的资源路径
	 * @return 静态资源路径
	 */
	private Path findStaticResourcePath(String path) {
		for (Path staticResourcePath : staticResourcePathSet) {
			if (staticResourcePath.match(path)) {
				return staticResourcePath;
			}
		}
		return null;
	}

	/**
	 * 获取默认的Controller
	 * @param path 请求路径
	 * @return 默认的Controller
	 */
	private Controller findDefaultController(String path) {
		return defaultControllerMap.get(path);
	}
}
